﻿using System;
using System.Diagnostics;
using ReadyApps.Common;
using ReadyApps.Common.Exceptions;
using ReadyApps.Data;
using ReadyApps.Data.Wiki;

namespace ReadyApps.Wiki
{
    /// <summary>
    /// Manages all activities related to permissions within the wiki control system.
    /// </summary>
    class WikiSecurityManager : IWikiSecurityManager
    {
        #region Variables

        private string username;
        private IWikiRoleManager roleManager;
        private IWikiDataProvider dataProvider;
        private EffectivePermissions effectivePermissions;

        #endregion

        #region Properties

        public bool IsUserAuthenticated
        {
            get { return roleManager.IsUserAuthenticated; }
        }

        public string CurrentUsername
        {
            get { return username; }
        }

        /// <summary>
        /// Gets a value indicating whether or not the user can view the wiki page content.
        /// </summary>
        public bool CanViewPage
        {
            get
            {
                return effectivePermissions.ViewPage ||
                    effectivePermissions.EditPage ||
                    effectivePermissions.CreatePage ||
                    effectivePermissions.CreateFolder ||
                    effectivePermissions.ManageRevisions ||
                    effectivePermissions.ManagePublishing ||
                    effectivePermissions.EditPermissions;
            }
        }

        /// <summary>
        /// Gets a value indicating whether or not the user can edit the wiki page content.
        /// </summary>
        public bool CanEditPage
        {
            get { return effectivePermissions.EditPage || CanManagePublishing || CanCreatePage; }
        }

        public bool CanCreatePage
        {
            get { return effectivePermissions.CreatePage || CanCreateFolder; }
        }

        public bool CanCreateFolder
        {
            get { return effectivePermissions.CreateFolder; }
        }

        /// <summary>
        /// Gets a value indicating whether or not the user can edit the wiki page permissions.
        /// </summary>
        public bool CanEditPermissions
        {
            get { return effectivePermissions.EditPermissions; }
        }

        /// <summary>
        /// Gets a value indicating whether or not the user can manage the revisions of a wiki
        /// page.
        /// </summary>
        public bool CanManageRevisions
        {
            get { return effectivePermissions.ManageRevisions; }
        }

        public bool CanManagePublishing
        {
            get { return effectivePermissions.ManagePublishing; }
        }

        public bool CanEditSettings
        {
            get { return CanManageRevisions || CanManagePublishing; }
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Creates a new <see cref="WikiSecurityManager"/> based on the given username and using the provided
        /// <see cref="IWikiDataProvider"/> and <see cref="IWikiRoleManager"/> to determine appropriate permissions.
        /// </summary>
        /// <param name="username">The logged in user's username</param>
        /// <param name="dataProvider">An <see cref="IWikiDataProvider"/> to retrieve permissions information from
        /// the data source</param>
        /// <param name="roleManager">An <see cref="IWikiRoleManager"/> to determine the user's role membership</param>
        public WikiSecurityManager(string username, IWikiDataProvider dataProvider, IWikiRoleManager roleManager)
        {
            TraceManager.BeginMethod(TraceComponent.SecurityManager, "Ctor");

            if (username == null) // Empty string is an acceptable value
            {
                throw new ArgumentNullException("username");
            }
            if (dataProvider == null)
            {
                throw new ArgumentNullException("dataProvider");
            }
            if (roleManager == null)
            {
                throw new ArgumentNullException("roleManager");
            }
            this.username = username;
            this.roleManager = roleManager;
            this.dataProvider = dataProvider;
            this.effectivePermissions = new EffectivePermissions();

            TraceManager.EndMethod(TraceComponent.SecurityManager, "Ctor");
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Sets the user's effective permissions for the given wiki page based on whether or not
        /// security has been enabled globally.
        /// </summary>
        /// <param name="securityIsEnabled">A value indicating if security has been enabled for the
        /// entire wiki site</param>
        /// <param name="pageID">The wiki page ID to generate effective permissions for</param>
        public void DeterminePagePermissions(bool securityIsEnabled, int pageID)
        {
            TraceManager.BeginMethod(TraceComponent.SecurityManager, "DeterminePagePermissions");

            // If security is enabled wiki site-wide, process permissions; otherwise supply
            // the default effective permissions
            if (securityIsEnabled)
            {
                bool pageSpecificPermissions;
                PermissionSet pagePermissions = dataProvider.GetPagePermissions(pageID, out pageSpecificPermissions);
                if (pageSpecificPermissions)
                {
                    effectivePermissions = GetEffectivePermissions(pagePermissions, dataProvider.GetFolderPermissionsForPage(pageID));
                }
                else
                {
                    effectivePermissions = GetEffectivePermissions(pagePermissions);
                }
            }
            else
            {
                effectivePermissions = EffectivePermissions.NonSecurityEnabledPermissions;
            }
            TraceManager.VariableValue(TraceComponent.SecurityManager, "EffectivePermissions", effectivePermissions);

            TraceManager.EndMethod(TraceComponent.SecurityManager, "DeterminePagePermissions");
        }

        public void DetermineFolderPermissions(bool securityIsEnabled, int folderID)
        {
            TraceManager.BeginMethod(TraceComponent.SecurityManager, "DetermineFolderPermissions");

            // If security is enabled wiki site-wide, process permissions; otherwise supply
            // the default effective permissions
            if (securityIsEnabled)
            {
                effectivePermissions = GetEffectivePermissions(dataProvider.GetFolderPermissions(folderID));
            }
            else
            {
                effectivePermissions = EffectivePermissions.NonSecurityEnabledPermissions;
            }
            TraceManager.VariableValue(TraceComponent.SecurityManager, "EffectivePermissions", effectivePermissions);

            TraceManager.EndMethod(TraceComponent.SecurityManager, "DetermineFolderPermissions");
        }

        /// <summary>
        /// Determines whether or not a specific <see cref="WikiPageMode"/> is available to a user
        /// based on the effective permissions.
        /// </summary>
        /// <param name="pageMode">The <see cref="WikiPageMode"/> to check for</param>
        /// <returns><c>True</c> if the user is allowed to execute the wiki page in the given page
        /// mode or <c>false</c> otherwise.</returns>
        public bool IsWikiPageModeAuthorized(WikiPageMode pageMode)
        {
            TraceManager.BeginMethod(TraceComponent.SecurityManager, "IsWikiPageModeAuthorized");

            bool pageModeAuthorized = false;
            switch (pageMode)
            {
                case WikiPageMode.Unknown:
                case WikiPageMode.ViewPage:
                    pageModeAuthorized = CanViewPage;
                    break;
                case WikiPageMode.EditPage:
                    pageModeAuthorized = CanEditPage;
                    break;
                case WikiPageMode.CreatePage:
                    pageModeAuthorized = CanCreatePage;
                    break;
                case WikiPageMode.EditPermissions:
                    pageModeAuthorized = CanEditPermissions;
                    break;
                case WikiPageMode.ViewRevisionList:
                case WikiPageMode.ViewPageRevision:
                    pageModeAuthorized = CanManageRevisions;
                    break;
                case WikiPageMode.ViewPagePending:
                    pageModeAuthorized = CanViewPage || CanManagePublishing;
                    break;
                case WikiPageMode.PageSettings:
                    pageModeAuthorized = CanEditSettings;
                    break;
                default:
                    throw new InvalidEnumerationValueException(typeof(WikiPageMode), pageMode);
            }

            TraceManager.EndMethod(TraceComponent.SecurityManager, "IsWikiPageModeAuthorized");
            return pageModeAuthorized;
        }

        public bool IsSameUser(string username)
        {
            TraceManager.BeginMethod(TraceComponent.SecurityManager, "");

            Debug.Assert(IsUserAuthenticated, "User is not authenticated which is unsupported for publishing.");

            bool same;
            try
            {
                same = CurrentUsername.Trim().ToUpper().Equals(username.Trim().ToUpper());
            }
            catch (NullReferenceException ex)
            {
                throw new ArgumentNullException("Username parameter was null", ex);
            }

            TraceManager.EndMethod(TraceComponent.SecurityManager, "");
            return same;
        }

        #endregion

        #region Private Methods

        private EffectivePermissions GetEffectivePermissions(PermissionSet pagePermissions, PermissionSet folderPermissions)
        {
            EffectivePermissions permissions = new EffectivePermissions();

            foreach (var rolePrivilege in pagePermissions.RolePrivileges)
            {
                if (roleManager.IsUserInRole(username, rolePrivilege.RoleName))
                {
                    permissions.Apply(rolePrivilege.Privileges, WikiPermissions.PageLevelPrivileges);
                }
            }
            foreach (var rolePrivilege in folderPermissions.RolePrivileges)
            {
                if (roleManager.IsUserInRole(username, rolePrivilege.RoleName))
                {
                    permissions.Apply(rolePrivilege.Privileges, WikiPermissions.FolderLevelPrivileges);
                }
            }

            return permissions;
        }

        private EffectivePermissions GetEffectivePermissions(PermissionSet folderPermissions)
        {
            EffectivePermissions permissions = new EffectivePermissions();

            foreach (var rolePrivilege in folderPermissions.RolePrivileges)
            {
                if (roleManager.IsUserInRole(username, rolePrivilege.RoleName))
                {
                    permissions.Apply(rolePrivilege.Privileges);
                }
            }

            return permissions;
        }

        #endregion
    }
}
